#Pyton library imports
from itertools import product
import re
#External libraries imports
from pyparsing import *
from numpy import array
#Internal imports
import FSPScopeHandling as scope
import FSPProcess as proc
from ParsingUtilities import *
from FSPSymbolParser import FSPSymbol

lowercaseRegex = "[a-z][a-zA-Z0-9]*"

uppercaseRegex = "[A-Z][a-zA-Z0-9]*"

LowercaseIdentifier = Regex(lowercaseRegex)

UppercaseIdentifier = Regex(uppercaseRegex)

ConstantIdentifier = FSPSymbol(uppercaseRegex, "const", scope.globalNamespace)

RangeIdentifier = FSPSymbol(uppercaseRegex, "range", scope.globalNamespace)

SetIdentifier = FSPSymbol(uppercaseRegex, "set", scope.globalNamespace)

VariableIdentifier = FSPSymbol(lowercaseRegex, "var", scope.globalNamespace)

ProcessIdentifier = FSPSymbol(uppercaseRegex, "process", scope.globalNamespace)

IncompleteProcessIdentifier = FSPSymbol(uppercaseRegex, "incomplete_process",
                                        scope.globalNamespace)

ParameterIdentifier = UppercaseIdentifier

Identifier = ConstantIdentifier ^ RangeIdentifier

#TESTIAMO LE THEANO EXPRESSIONS! from FSPExpressionParser import Expression
from TheanoExprSqueeze import Expression

endStack = []

def push(s,l,t):
  if l != 0: endStack.append(l)
  else: pass

ConstantDefinition =\
  (
    Suppress(Keyword("const")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    Expression #da qui deve uscire un int
  ).setParseAction(lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0],
                                                                    t[1],
                                                                    "const"),
                   push)

RangeDefinition =\
  (
    Suppress(Keyword("range")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    Expression + lit("..") + Expression #devono uscire due int
  ).setParseAction\
  (
    lambda s, l, t:
      scope.currentNamespace.addObject(s, l, t[0], range(t[1], t[2] + 1), "range"),
    push
  )

RangeExpression =\
  (
    (Expression + lit("..") + Expression) #devono uscire due int
    .setParseAction(lambda x: range(x[0], x[1] + 1))
  )

Range = RangeIdentifier ^ RangeExpression

ActionLabels = Forward()

SetElements =\
  (
    ActionLabels + ZeroOrMore(lit(",") + ActionLabels)
  ).setParseAction(lambda l: set([i for i in l]))

SetExpression =\
  (lit("{") + SetElements + lit("}"))

SetDefinition =\
  (
    Suppress(Keyword("set")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    SetExpression
  ).setParseAction\
  (lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0], t[1], "set"), push)

Set = SetIdentifier ^ SetExpression

ActionLabel =\
  (
    Combine
    (
      (LowercaseIdentifier ^ (Literal("[") + Expression + Literal("]"))) +
      ZeroOrMore(
        (
          (Literal(".") + LowercaseIdentifier)
          ^
          (Literal("[") + Expression + Literal("]"))
        )
      )
    )
  )

def expandActionLabels(components):
  components = components.asList()
  components = [{c} if type(c) == str else set(c) for c in components]
  strComponents = []
  for s in components:
    for e in s:
      if type(e) != str:
        s = s.difference({e}).union({str(e)})
    strComponents.append(s)
  strComponents = [''.join(e) for e in product(*strComponents)]
  return strComponents

VariableDefinition =\
  (
    LowercaseIdentifier + lit(":") + Range
  ).setParseAction(lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0],
                                                        {str(i) for i in t[1]},
                                                        "var"),
                   lambda s, l, t: scope.currentNamespace.getName(s, l, t[0], "var"))

ActionRange =\
  (
    SetIdentifier
    ^SetExpression
    ^(RangeIdentifier ^ RangeExpression)
    .copy().setParseAction(lambda t: {str(i) for i in t[0]})
    ^VariableDefinition
    # (Variable >> lit(":") >> Set)   |
  )

ActionLabels <<\
  (
    (ActionLabel ^ Set ^ (Literal("[") + ActionRange + Literal("]"))) +
    ZeroOrMore
    (
      (Literal(".") + ActionLabel) ^
      (Literal(".") + Set) ^
      (Literal("[") + ActionRange + Literal("]")) ^
      Combine(Literal("[") + Expression + Literal("]"))
    )
  ).setParseAction\
  (
    lambda t: expandActionLabels(t),
    lambda s, l, t: proc.FSPProcess.expandCurrentAlphabet(s, l, t)
  )

#Strange things here!

PrefixActions = ActionLabels + ZeroOrMore(lit("->") + ActionLabels)

Guard = Suppress(Keyword("when")) + Expression

LocalProcess = Forward()

ActionPrefix =\
  Optional(Guard) + PrefixActions + lit("->") + LocalProcess

Indices = OneOrMore(Literal("[") + Expression + Literal("]"));
IndexRanges = OneOrMore(Literal("[") + (Expression ^ ActionRange) + Literal("]"))

ParameterDefinition =\
  (
    (ParameterIdentifier + lit("=") + Expression).copy().setParseAction
    (
      lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0], t[1], "par"),
      lambda t: {t[0]: t[1]}
    )
  )

Parameters =\
  (
    lit("(") +
    ParameterDefinition + ZeroOrMore(lit(",") + ParameterDefinition) +
    lit(")")
  ).setParseAction(lambda t: {k: d[k] for d in t for k in d.keys()})

BaseLocalProcess =\
  (
    Keyword("END") ^ Keyword("STOP") ^ Keyword("ERROR") ^
    (
      (ProcessIdentifier + Optional(Indices))
      |
      (IncompleteProcessIdentifier + Optional(Indices))
    )
  )

Choice = ActionPrefix + ZeroOrMore(lit("|") + ActionPrefix)

LocalProcess <<\
  (
    BaseLocalProcess ^
    #SequentialComposition ^
    #if Expression then LocalProcess ^
    #if Expression then LocalProcess else LocalProcess ^
    (lit("(") + Choice + lit(")"))
  )

LocalProcessDefinition =\
  (
    ProcessIdentifier + Optional(IndexRanges) + lit("=") + LocalProcess
  )

ProcessBody =\
  (
    LocalProcess + ZeroOrMore(lit(",") + LocalProcessDefinition)
  )

AlphabetExtension = (lit("+") + Set).setParseAction(
  lambda s, l, t: proc.FSPProcess.expandCurrentAlphabet(s, l, t[0]))

RelabelRule =\
  (
    Group(ActionLabels) +
    lit("/") +
    Group(ActionLabels)
  ).setParseAction(
  lambda s,l,t: proc.FSPProcess.relabelCurrentAlphabet(s, l, t[0], t[1]))
#oppure forall IndexRanges { RelabelDefs }

RelabelRules = RelabelRule + ZeroOrMore(lit(",") + RelabelRule)

Relabel = lit("/") + lit("{") + RelabelRules + lit("}")

SelectiveExposition =\
  (
    (lit("@") + Set).setParseAction
    (lambda s,l,t: proc.FSPProcess.hideCurrentAlphabet(s, l, t,"expose"))
  )

SelectiveHiding =\
  (
    (lit("\\") + Set)
    .setParseAction
    (lambda s,l,t: proc.FSPProcess.hideCurrentAlphabet(s, l, t[0],"hide"))
  )

Hiding = SelectiveHiding ^ SelectiveExposition

ProcessDefinition =\
  (
    UppercaseIdentifier.copy().setParseAction
    (
      scope.setGlobalNamespace,
      lambda s, l, t: scope.currentNamespace.addName(s, l, t[0],
                                                     FSPType="incomplete_process"),
      lambda t: proc.FSPProcess(t[0]),
      lambda t: scope.setNamespace(t[0].namespace, t[0])
    ) +
    Optional(Parameters) +
    lit("=") +
    ProcessBody +
    Optional(AlphabetExtension) +
    Optional(Relabel) +
    Optional(Hiding) +
    lit(".")
  ).setParseAction\
  (
    scope.setGlobalNamespace,
    lambda s, l, t: scope.currentNamespace.bindName(s, l, name=t[0].processId,
                                                    value=t[0],
                                                    FSPType="process")
  )
